// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

//define avmm encoded events here
`define AVMM_IDLE    8'h00
`define AVMM_WR_REQ  8'h01
`define AVMM_RD_REQ  8'h02
`define AVMM_WR_CMPL 8'h04
`define AVMM_RD_CMPL 8'h08

module lvds_mm_sm_master
    #(
        parameter  DATA_WIDTH    = 32,
        parameter  ADDR_WIDTH    = 16,
        parameter  TIMEOUT_PARAM = 25000
    ) 
(
    input                           clk,
    input                           reset,
    
    //Master Agent interface, Only exist on slave side
    output reg [ADDR_WIDTH-1:0]     mstr_agent_address,
    output reg                      mstr_agent_read,
    output reg                      mstr_agent_write,
    output reg [DATA_WIDTH-1:0]     mstr_agent_writedata,
    output reg [(DATA_WIDTH/8)-1:0] mstr_agent_byteenable,
    input      [DATA_WIDTH-1:0]     mstr_agent_readdata,
    input                           mstr_agent_readdatavalid,
    input                           mstr_agent_waitrequest,
        
    //Encoding AVMM events to ioc frame
    output reg  [7:0]               ioc_frame_o,
    input       [7:0]               ioc_frame_i,
    //tx/rx frame cnt
    input       [11:0]              tx_frm_offset,
    input       [11:0]              rx_frm_offset,
    
    //Sideband Signals
    output reg                      crc_check_pass,   //1'b1: Pass; 1'b0: Fail.
    output                          avmm_timeout,
    output reg  [31:0]              crc_err_cnt,      //Record how many crc_error has been happened
    output reg  [31:0]              timeout_cnt       //Record how many timeout event has been happened
);

    localparam IDLE          = 8'h00;
    localparam TX_WR_CMPL    = 8'h01;    
    localparam RX_ADDR_B1    = 8'h02;
    localparam RX_ADDR_B0    = 8'h03;
    localparam RX_DATA_B3    = 8'h04;
    localparam RX_DATA_B2    = 8'h05;
    localparam RX_DATA_B1    = 8'h06;
    localparam RX_DATA_B0    = 8'h07;
    localparam CRC_SEND      = 8'h08;
    localparam CRC_CLR_WAIT  = 8'h09;
    localparam PREPARE_CMPL  = 8'h0A;
    localparam TX_DATA_B3    = 8'h0B;
    localparam TX_DATA_B2    = 8'h0C;
    localparam TX_DATA_B1    = 8'h0D;
    localparam TX_DATA_B0    = 8'h0E;
    localparam CRC_CHECK     = 8'h0F;
    localparam TX_RD_CMPL    = 8'h10;
    localparam ASSERT_AVMM   = 8'h11;

reg  [ 7:0]  crc_data_in;     //bytes used to update CRC value
wire [15:0]  crc_out;         //CRC result    

reg  [ 7:0]  state;
reg          write_cmd_store; //store if current transaction is AVMM write
reg          read_cmd_store;  //store if current transaction is AVMM read
reg  [15:0]  address_hold; 
reg  [31:0]  writedata_hold;
reg  [31:0]  readdata_hold;
reg          crc_gen_clr;
reg          tx_crc_cal;       //Indicate current tx_byte should be used to update CRC
reg          rx_crc_cal;       //Indicate current rx_byte should be used to update CRC
reg  [15:0]  crc_in;           //Initial and Previous crc value
reg  [15:0]  avmm_timer;

//Control State Machine
always @ (posedge clk) begin
    if (reset) begin
        ioc_frame_o           <= `AVMM_IDLE; //Send IDLE on the frame when no transaction.    
        write_cmd_store       <= 1'b0;
        read_cmd_store        <= 1'b0;
        address_hold          <= 16'b0;
        writedata_hold        <= 16'b0;
        readdata_hold         <= 16'b0;
        crc_gen_clr           <= 1'b1; // clear crc initial value
        crc_err_cnt           <= 16'b0;
        timeout_cnt           <= 16'b0;
        mstr_agent_address    <= 'b0;
        mstr_agent_read       <= 1'b0;
        mstr_agent_write      <= 1'b0;
        mstr_agent_writedata  <= 'b0;
        mstr_agent_byteenable <= 4'b1111; 
        crc_check_pass        <= 1'b1;        
        state                 <= IDLE;
    end else begin
        case (state)
            IDLE: begin            
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o           <= `AVMM_IDLE; //Send IDLE on the frame when no transaction.    
                end
                write_cmd_store   <= 1'b0;
                read_cmd_store    <= 1'b0;
                address_hold      <= 16'b0;
                writedata_hold    <= 16'b0;
                readdata_hold     <= 16'b0;
                crc_gen_clr       <= 1'b1; // clear crc initial value 
                tx_crc_cal        <= 1'b0;
                rx_crc_cal        <= 1'b0;
                crc_check_pass    <= 1'b1;
                
                if (rx_frm_offset%12 == 0) begin
                    if ( (ioc_frame_i == `AVMM_WR_REQ || ioc_frame_i == `AVMM_RD_REQ) && !mstr_agent_waitrequest ) begin
                        rx_crc_cal      <= 1'b1;
                        write_cmd_store <= ( ioc_frame_i == `AVMM_WR_REQ );
                        read_cmd_store  <= ( ioc_frame_i == `AVMM_RD_REQ );
                        state           <= RX_ADDR_B1;
                    end
                end            
            end
                        
            RX_ADDR_B1: begin //store address, 2 bytes, MSB First
                crc_gen_clr        <= 1'b0;
                rx_crc_cal         <= 1'b1;
                
                if (rx_frm_offset%12 == 0) begin
                    address_hold[15:8] <= ioc_frame_i;
                    state              <= RX_ADDR_B0;
                end
            end
            
            RX_ADDR_B0: begin 
                rx_crc_cal         <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    address_hold[ 7:0] <= ioc_frame_i;
                    
                    if (write_cmd_store) begin
                        state <= RX_DATA_B3;
                    end else begin
                        state <= CRC_CHECK;
                    end
                end
            end            
            
            RX_DATA_B3: begin
                rx_crc_cal            <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    writedata_hold[31:24] <= ioc_frame_i;
                    state                 <= RX_DATA_B2;
                end
            end
            
            RX_DATA_B2: begin
                rx_crc_cal            <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    writedata_hold[23:16] <= ioc_frame_i;
                    state                 <= RX_DATA_B1;
                end
            end
            
            RX_DATA_B1: begin
                rx_crc_cal            <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    writedata_hold[15: 8] <= ioc_frame_i;
                    state                 <= RX_DATA_B0;
                end
            end

            RX_DATA_B0: begin
                rx_crc_cal            <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    writedata_hold[ 7: 0] <= ioc_frame_i;
                    state                 <= CRC_CHECK;
                end
            end            
            
            CRC_CHECK: begin
                if (rx_frm_offset%12 == 0) begin                
                    rx_crc_cal       <= 1'b0;
                    tx_crc_cal       <= 1'b0;
                
                    crc_check_pass   <= ( ioc_frame_i == crc_out[7:0] );                
                    if ( ioc_frame_i != crc_out[7:0] ) begin
                        crc_err_cnt <= crc_err_cnt + 1'b1;
                        state       <= IDLE;                    
                    end else if (!mstr_agent_waitrequest) begin //CRC check pass and no waitrequest, begin to assert AVMM_mst intf  
                        state       <= ASSERT_AVMM;
                    end
                end
            end
            
            ASSERT_AVMM: begin //assert AVMM master bus for one clock cycle
                mstr_agent_address    <= address_hold;
                mstr_agent_writedata  <= writedata_hold;
                
                if(write_cmd_store) begin         
                    mstr_agent_read       <= 1'b0;
                    mstr_agent_write      <= 1'b1;               
                end else begin
                    mstr_agent_read       <= 1'b1;
                    mstr_agent_write      <= 1'b0; 
                end
                state       <= PREPARE_CMPL;
            end
            
            PREPARE_CMPL: begin
                crc_gen_clr           <= 1'b1;
                mstr_agent_address    <= 'b0;
                mstr_agent_read       <= 1'b0;
                mstr_agent_write      <= 1'b0;
                mstr_agent_writedata  <= 'b0;
                                
                if (avmm_timeout) begin
                    timeout_cnt   <= timeout_cnt + 1'b1;
                    state         <= IDLE; 
                end else if (write_cmd_store) begin
                    crc_gen_clr   <= 1'b0;
                    state         <= TX_WR_CMPL; 
                end else if ( read_cmd_store && mstr_agent_readdatavalid ) begin
                    crc_gen_clr   <= 1'b0;
                    readdata_hold <= mstr_agent_readdata;
                    state         <= TX_RD_CMPL;
                end
            end
            
            TX_WR_CMPL: begin
                tx_crc_cal  <= 1'b1;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= `AVMM_WR_CMPL;
                    state       <= CRC_SEND;
                end
                
            end
            
            TX_RD_CMPL: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= `AVMM_RD_CMPL;
                    state       <= TX_DATA_B3;
                end
            end

            TX_DATA_B3: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= readdata_hold[31:24];
                    state       <= TX_DATA_B2;
                end
            end

            TX_DATA_B2: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= readdata_hold[23:16];
                    state       <= TX_DATA_B1;
                end
            end            

            TX_DATA_B1: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= readdata_hold[15: 8];
                    state       <= TX_DATA_B0;
                end
            end            

            TX_DATA_B0: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= readdata_hold[ 7: 0];
                    state       <= CRC_SEND;
                end
            end            
                    
            CRC_SEND: begin
                if (tx_frm_offset%12 == 6) begin
                    tx_crc_cal  <= 1'b0;
                    ioc_frame_o <= crc_out[7:0];  
                    state       <= IDLE;
                end
                
            end
            
            default: begin
                ioc_frame_o     <= `AVMM_IDLE;
                state           <= IDLE;
            end
        endcase
    end //non reset    
end

//Watchdog Timer
always @ (posedge clk) begin
    if (reset) begin
        avmm_timer <= 16'h0;
    end else if ( state == IDLE ) begin
        avmm_timer <= 16'h0;
    end else if ( state == PREPARE_CMPL ) begin
        avmm_timer <= avmm_timer + 1'b1;
    end
end

assign avmm_timeout = avmm_timer == TIMEOUT_PARAM;

//CRC Generator_Checker
crc16_dat8 crc_gen_check
(
    .crc_in   ( crc_in      ),
    .dat_in   ( crc_data_in ), 
    .crc_out  ( crc_out     )
);

always @ (posedge clk)
  if (reset)
    crc_in <= 16'h0;
  else if (crc_gen_clr)
    crc_in <= 16'h0;
  else if ( (tx_crc_cal&(tx_frm_offset%12 == 9)) | (rx_crc_cal&(rx_frm_offset%12 == 2)) )
    crc_in <= crc_out;

always @ (posedge clk) begin
    if (reset) begin
        crc_data_in <= 8'h0;
    end else if (crc_gen_clr) begin
        crc_data_in <= 'h0;
    end else if ( rx_crc_cal&(rx_frm_offset%12 == 2) ) begin
        crc_data_in <= ioc_frame_i;
    end else if ( tx_crc_cal&(tx_frm_offset%12 == 9) ) begin
        crc_data_in <= ioc_frame_o;
    end else begin
        crc_data_in <= crc_data_in;
    end
end

endmodule    